package hs.framework.opcClient.utils;

import hs.framework.opcClient.service.impl.TypeClassServiceImpl;
import lombok.extern.log4j.Log4j2;
import org.jinterop.dcom.common.JIException;
import org.jinterop.dcom.core.JIVariant;
import org.openscada.opc.dcom.list.ClassDetails;
import org.openscada.opc.lib.da.*;
import org.openscada.opc.lib.list.Categories;
import org.openscada.opc.lib.list.Category;
import org.openscada.opc.lib.list.ServerList;

import java.util.*;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @Description TODO
 * @Author sxmz
 * @Date 2021/5/14 0014 下午 3:29
 */
@Log4j2
public class ServerUtil {
    //方法执行次数计数
    public static int funNum = 0;
    //方法最大执行次数
    public static int funNumMax = 5;
    /**
    * @Description 读单个item
    * @Author sxmz
    * @Date 2021/5/14 0014 下午 3:39
    **/
    public static DataItem readSync(Server server, String itemId){
        //TODO 同步读取数据
        try {
            Group group = server.addGroup("read");
            Map<String, Item> itemMap = group.addItems(itemId);
            Item item = itemMap.get(itemId);
            ItemState itemState = item.read(false);
            DataItem dataItem = JiVariantUtil.parseValue(item.getId(), itemState);
            group.clear();
            server.removeGroup(group,true);
            return dataItem;
        } catch (Exception e) {
            log.error("同步读取失败！", e);
            return null;
        }
    }

    /**
    * @Description 读多个item
    * @Author sxmz
    * @Date 2021/5/14 0014 下午 3:40
    **/
    public static ListResult<DataItem> readSync(Server server, Collection<String> itemIds){
        //TODO 同步读取数据
        List<DataItem> result = new ArrayList<>();
        List<DataItem> noresult = new ArrayList<>();
        try {
//            Group group = server.addGroup("read");
            Group group = server.findGroup("read");
            Map<String, Item> itemMap = group.addItems(itemIds.toArray(new String[0]));
            for(Map.Entry<String, Item> entry: itemMap.entrySet()){
                try {
                    Item item = entry.getValue();
                    ItemState itemState = item.read(false);
                    DataItem dataItem = JiVariantUtil.parseValue(item.getId(), itemState);
                    result.add(dataItem);
                }catch (Exception e) {
                    log.error(entry.getKey()+"同步读取失败！", e);
                    noresult.add(new DataItem(entry.getKey(),null,null,null,null,new Date()));
                }
            }
            group.clear();
//            server.removeGroup(group,true);
        }
        catch (UnknownGroupException e){
            log.error("未创建 read group，开始创建...",e);
            //避免死循环，创建全局变量计数
            if (funNum < funNumMax){
                funNum++;
                Group group = server.addGroup("read");
                readSync(server,itemIds);
            }else {
                funNum = 0;
            }
        }
        catch (Exception e) {
            log.error("同步读取失败！", e);
        }finally {
            return ListResult.build(result,noresult);
        }
    }

    public static ListResult<DataItem> readSync(Server server){
        try {
            return readSync(server, browseItemIds(server));
        } catch (Throwable throwable) {
            log.error("同步读取失败！", throwable);
            return null;
        }
    }

    /**
     * 异步读取数据（可指定节点）
     * @param server OPC服务
     * @param itemIds 节点编号集合
     * @param threadPool 线程池
     * @param heartBeat 心跳时间：小于0表示只接收一次，大于0则表示循环接收
     * @param dataCallback 数据接收后的回调处理
     * @throws Throwable
     */
    public static void readAsyn(Server server, Collection<String> itemIds, ScheduledExecutorService threadPool, long heartBeat, Browser.DataCallback dataCallback) throws Throwable {
        Group group = server.addGroup();
        Map<String, Item> items = group.addItems(itemIds.toArray(new String[0]));

        Runnable runnable = ()->{
            try {
                List<DataItem> dataList = new ArrayList<>();
                for(Map.Entry<String, Item> entry: items.entrySet()){
                    ItemState read = entry.getValue().read(false);
                    //转换格式并添加到结果
                    dataList.add(JiVariantUtil.parseValue(entry.getKey(), read));
                }
                //数据处理器回调
                dataCallback.process(dataList);
            } catch (Throwable e) {
                log.error("读取数据时发生异常！", e);
            }
        };
        //如果心跳时间为 -1，则表示不循环查询
        if(heartBeat <= 0L){
            threadPool.submit(runnable);
        }else{
            threadPool.scheduleAtFixedRate(runnable, 1,heartBeat, TimeUnit.MILLISECONDS);
        }

    }

    /**
     * 异步读取数据（查询所有节点,重复查询）<br>
     * 该方法仅调用 {@link Browser#readAsyn(Server, Collection, ScheduledExecutorService, long, Browser.DataCallback)}
     * @param server OPC服务
     * @param threadPool 线程池
     * @param heartBeat 重复查询的心跳时间
     * @param dataCallback 接收到数据后的回调处理
     * @throws Throwable
     */
    public static void readAsyn(Server server, ScheduledExecutorService threadPool,long heartBeat, Browser.DataCallback dataCallback) throws Throwable {
        readAsyn(server, browseItemIds(server), threadPool, heartBeat, dataCallback);
    }

    /**
     * 异步读取数据（查询所有节点,只查询一次）<br>
     * 该方法仅调用 {@link Browser#readAsyn(Server, Collection, ScheduledExecutorService, long, Browser.DataCallback)}
     * @param server OPC服务
     * @param threadPool 线程池
     * @param dataCallback 接收到数据后的回调处理
     * @throws Throwable
     */
    public static void readAsyn(Server server, ScheduledExecutorService threadPool, Browser.DataCallback dataCallback) throws Throwable {
        readAsyn(server, browseItemIds(server), threadPool, -1L, dataCallback);
    }

    public static void subscibe(Server server){
        //TODO 订阅指定数据，只有数据改变才会触发

    }

    /**
     * 获取所有节点的编号
     * @param server
     * @return
     * @throws Throwable
     */
    public static Collection<String> browseItemIds(Server server) throws Throwable{
        Collection<String> nodeIds = server.getFlatBrowser().browse();
        return nodeIds;
    }

    /**
     * 罗列出目标主机上的OPC服务器软件
     * @param host
     * @param domain
     * @param userName
     * @param password
     * @return
     * @throws Throwable
     */
    public static List<ServerInfo> listServer(String host, String domain, String userName, String password) throws Throwable {
        ServerList serverList = new ServerList(host, userName, password, domain);
        Collection<ClassDetails> classDetails = serverList.listServersWithDetails(new Category[]{Categories.OPCDAServer20}, new Category[]{});
        List<ServerInfo> serverInfos = new ArrayList<>();
        System.out.println("在目标主机上发现如下OPC服务器：");
        for(ClassDetails details: classDetails){
            serverInfos.add(new ServerInfo(details.getProgId(), details.getClsId(), details.getDescription()));
            System.out.format("\tprogId: '%s' \r\n\tclsId：'%s' \r\n\tdescription:'%s' \r\n\r\n", details.getProgId(), details.getClsId(), details.getClsId());
        }
        return serverInfos;
    }

    public static void browserServer(){
        //TODO 获取服务器的基本信息
    }
    /**
     * @Description 向服务器修改item
     * @Author sxmz
     * @Date 2021/5/14 0014 下午 1:49
     **/
    public static ListResult<DataItem> writeValue(Server server, List<DataItem> dataItems){
        List<DataItem> resultList = new ArrayList<>();
        List<DataItem> noresultList = new ArrayList<>();
        try {
            Group group = server.findGroup("write");
            for (DataItem dataItem :dataItems) {
                try {
                    // Add a new item to the group，
                    // 将一个item加入到组，item名字就是MatrikonOPC Server或者KEPServer上面建的项的名字比如：u.u.TAG1，PLC.S7-300.TAG1
                    final Item item = group.addItem(dataItem.getItemId());
                    //根据type做数据转型处理
                    Object o = dataItem.getValue();
                    //查找数据库进行类型对应
                    String classPath = TypeClassServiceImpl.getClassPath(dataItem.getDataType());
                    o = TypeChangeUtil.change(o,Class.forName(classPath));
                    final JIVariant value = JIVariant.makeVariant(o,true);
                    int result = item.write(value);
                    DataItem res = JiVariantUtil.parseValue(item.getId(), item.read(false));
                    resultList.add(res);
                } catch (Exception e) {
                    log.error("opc服务器写值失败！");
                    log.error(e.getMessage());
                    if (e instanceof JIException){
                        JIException ee = JIException.class.cast(e);
                        log.error(String.format("%08X: %s", ee.getErrorCode(), server.getErrorMessage(ee.getErrorCode())));
                    }
                    noresultList.add(dataItem);
                }
            }
            group.clear();
//            server.removeGroup(group,true);
        }
        catch (UnknownGroupException e){
            log.error("未创建 write group，开始创建...",e);
            //避免死循环，加入方法执行计数
            if (funNum < funNumMax){
                Group group = server.addGroup("write");
                writeValue(server,dataItems);
            }else {
                funNum = 0;
            }
        }
        catch (Exception e){
            log.error("opc服务器分组失败！");
            log.error(e.getMessage());
        }finally {
            return ListResult.build(resultList, noresultList);
        }

    }

    /**
     * 处理结果数据的回调
     */
    public interface DataCallback{
        /**
         * 数据处理
         * @param dataItems
         * @throws Throwable
         */
        void process(List<DataItem> dataItems) throws Throwable;
    }
}
